﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Text;
using VipSoft.Core.Driver;
using VipSoft.Core.Entity;

namespace VipSoft.Core.Utility
{
    public class Criterion
    {
        //private Map<String, SqlParameter> parameterMap;
        public IDriver Driver { get; set; }
        public IDictionary<string, DbParameter> DicParameter { get; set; }
        public IList<DbParameter> DbParameter { get; set; } //= new List<DbParameter>();
        #region Private

        private string OperationToString(ClassMetadata classMetadata)
        {
            var result = new StringBuilder();
            var columnName = "";
            var parameters = new List<DbParameter>();
            foreach (ColumnInfo columnInfo in classMetadata.ColumnInfos)
            {
                if (!columnInfo.IsColumn) continue;
                if (columnInfo.PropertyName.ToUpper() == Name.ToUpper())
                {
                    columnName = columnInfo.Name;
                }

                //if (IsNull(value)) continue;
                //if (IsNullValue(value) && columnInfo.IsNullable) continue;

                //var index = Contains(obj.Conditaion, columnInfo.PropertyName);
                //if (index == 0)
                //{
                //    rConditaion = rConditaion.Replace(string.Format(LikeString[0], columnInfo.PropertyName), entityPersister.Driver.FormatNameForSql(columnInfo.Name));
                //    entityPersister.SetDbParamter(session, parameters, columnInfo.Name, value);
                //    //entityPersister.SetDbParamter(session, parameters, classMetadata.PrimaryKey, value);
                //}
                //else if (index > 0)
                //{
                //    rConditaion = rConditaion.Replace(string.Format(LikeString[index], columnInfo.PropertyName), string.Format(LikeValueString[index - 1], Escape(value.ToSql(), "")));
                //}
            }

            var paramName = Driver.FormatNameForSql(columnName);
            switch (Operation)
            {
                case OP.EQ:
                    result.Append(columnName).Append("=").Append(paramName);
                    SetDbParamter(DbParameter, columnName, Value);
                    break;
                case OP.NE:
                    result.Append(columnName).Append("<>").Append(paramName);
                    SetDbParamter(DbParameter, columnName, Value);
                    break;
                case OP.GE:
                    string maxGE = new StringBuilder("Min_").Append(columnName).ToString();
                    var maxGEParam = Driver.FormatNameForSql(maxGE);
                    result.Append(columnName).Append(">=").Append(maxGEParam);
                    SetDbParamter(DbParameter, columnName, maxGEParam, Value); 
                    break;
                case OP.LE:
                    string minLE = new StringBuilder("Max_").Append(columnName).ToString();
                    var minLEParam = Driver.FormatNameForSql(minLE);
                    result.Append(columnName).Append("<=").Append(minLEParam);
                    SetDbParamter(DbParameter, columnName, minLEParam, Value); 
                    break;
                case OP.GT:
                    string maxGT = new StringBuilder("Min_").Append(columnName).ToString();
                    var maxGTParam = Driver.FormatNameForSql(maxGT);
                    result.Append(columnName).Append(">").Append(maxGTParam);
                    SetDbParamter(DbParameter, columnName, maxGTParam, Value);  
                    break;
                case OP.LT:
                    string minLT = new StringBuilder("Max_").Append(columnName).ToString();
                    var minLTParam = Driver.FormatNameForSql(minLT);
                    result.Append(columnName).Append("<").Append(minLTParam);
                    SetDbParamter(DbParameter, columnName,minLTParam, Value);
                    break;
                case OP.IN:
                    result.Append(columnName).Append(" IN (");
                    BuildInOperation(result, columnName);//Oracle 不能大于1000
                    result.Append(") ");
                    break;
                case OP.BETWEEN:
                    string min = new StringBuilder("Min_").Append(columnName).ToString();
                    string max = new StringBuilder("Max_").Append(columnName).ToString(); 
                    var minParam = Driver.FormatNameForSql(min);
                    var maxParam = Driver.FormatNameForSql(max);
                    result.Append("(").Append(columnName).Append(">=").Append(minParam);
                    result.Append(" AND ");
                    result.Append(columnName).Append("<=").Append(maxParam).Append(")");

                    if (Value is Array && (((Object[])Value).Length > 1))
                    {
                        var objects = (Object[])Value;
                        SetDbParamter(DbParameter, columnName, minParam, objects[0]);
                        SetDbParamter(DbParameter, columnName, maxParam, objects[1]);
                    }
                    break;                           
                case OP.LIKE:
                    result.Append(columnName).Append("  LIKE ").Append(paramName);
                    SetDbParamter(DbParameter, columnName, string.Format("%{0}%", Value));
                    break;
                case OP.FLIKE:
                    result.Append(columnName).Append("  LIKE ").Append(paramName);
                    SetDbParamter(DbParameter, columnName, string.Format("{0}%", Value));
                    break;
                case OP.ELIKE:
                    result.Append(columnName).Append(" LIKE ").Append(paramName);
                    SetDbParamter(DbParameter, columnName, string.Format("%{0}", Value));
                    break;
                //case OP.IS: 
                //    result.Append(columnName).Append(" IS ").Append(paramName);
                //    SetDbParamter(DbParameter, columnName, Value);
                //    break;
                //case OP.UEMPTY:                                    
                //    result.Append(columnName).Append(" IS NOT NULL");
                //    break;
            }
            return result.ToString();
        }
        private void BuildInOperation(StringBuilder sql, string columnName)
        {
            if (Value is Array && (((Object[])Value).Length > 0))
            {
                var objects = (Object[])Value;
                var index = 1;
                foreach (var obj in objects)
                {
                    var param = Driver.FormatNameForSql(columnName + index);
                    //sql.AppendFormat(obj is int ? "{0}," : "'{0}',", obj);
                    sql.AppendFormat("{0},", param);
                    SetDbParamter(DbParameter, columnName, param, obj);
                    index++;
                }
                sql.Remove(sql.Length - 1, 1);
            }

        }
        private void SetDbParamter(ICollection<DbParameter> paramters, string columnName, object vaule)
        {
            DbParameter result;
            DicParameter.TryGetValue(columnName, out result);
            if (result == null) return;
            var p = (DbParameter)((ICloneable)(result)).Clone();
            p.Size = result.Size;
            p.Value = vaule ?? DBNull.Value;
            paramters.Add(p);
        }

        private void SetDbParamter(ICollection<DbParameter> paramters, string columnName, string param, object vaule)
        {
            DbParameter result;
            DicParameter.TryGetValue(columnName, out result);
            if (result == null) return;
            var p = (DbParameter)((ICloneable)(result)).Clone();
            p.ParameterName = param;
            p.Size = result.Size;
            p.Value = vaule ?? DBNull.Value;
            paramters.Add(p);
        }

        #endregion

        #region Public

        #region Feild
        public LOP LogicOperation { get; set; }

        //属性名
        public String Name { get; set; }

        public Criteria Criteria { get; set; }

        public Type DataType { get; set; }

        public OP Operation { get; set; }

        public Object Value { get; set; }
        #endregion

        #region Constructor

        public Criterion(Criteria criteria)
        {
            LogicOperation = LOP.AND;
            Value = criteria;
        }

        public Criterion(String name, Object value)
        {
            //Criterion(LOP.AND, name, OP.EQ, value);
            this.LogicOperation = LOP.AND;
            Name = name;
            Operation = OP.EQ;
            Value = value;
        }

        public Criterion(String name, OP operation, Object value)
        {
            //Criterion(LOP.AND, name, operation, value);
            this.LogicOperation = LOP.AND;
            Name = name;
            Operation = operation;
            Value = value;
        }

        public Criterion(LOP logicOperation, Criteria criteria)
        {
            LogicOperation = logicOperation;
            Value = criteria;
        }

        public Criterion(LOP logicOperation, String name, OP operation, Object value)
        {
            this.LogicOperation = logicOperation;
            Name = name;
            Operation = operation;
            Value = value;
        }
        #endregion


        public string ToSql(ClassMetadata classMetadata, int index)
        {
            var sql = new StringBuilder();
            if (index > 0)
            {
                sql.Append(LogicOperation == LOP.OR ? " OR " : " AND ");
            }
            if (Value is Criteria)
            {
                var criteria = Value as Criteria;
                sql.Append("( ");
                sql.Append(criteria.ToSql(classMetadata));
                sql.Append(" )");
            }
            else
            {
                sql.Append(OperationToString(classMetadata));
            }
            return sql.ToString();
        }
        #endregion
    }
}
